// -----------------------------------------------------------------------------
// This source file is part of Matrix Platform
// 	(Universal .NET Software Development Platform)
// For the latest info, see http://www.matrixplatform.com
// 
// Copyright (c) 2009-2010, Ingenious Ltd
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Net;
#if Matrix_Diagnostics

using Matrix.Common.Core.Collections;
using Matrix.Common.Diagnostics.TracerCore;
using Matrix.Common.Diagnostics.TracerCore.Items;
using Matrix.Common.Diagnostics.TracerCore.Sinks;
using Matrix.Common.Sockets.Common;
using Matrix.Common.Sockets.Core;
using Matrix.Common.Core.Serialization;


namespace Matrix.Common.Sockets.NetworkTracer
{
    /// <summary>
    /// Extends the tracer class functionality, by providing networking capabilities
    /// (both importing trace items from other sources, or exporting to network consumers).
    /// </summary>
    public class NetworkTracer : Tracer, ITracerItemSink
    {
        public const int DefaultPort = 19627;

        volatile int _atConnectMaxItemsSendCount = 10000;
        
        /// <summary>
        /// The maximum numebr of items from history to send to a client when 
        /// client connects (uses a TracerItemKeeperSink, so if there is none
        /// this will not send any items).
        /// </summary>
        public int AtConnectMaxItemsSendCount
        {
            get { return _atConnectMaxItemsSendCount; }
            set { _atConnectMaxItemsSendCount = value; }
        }

        object _syncRoot = new object();
        
        SocketMessageServer _server = null;

        HotSwapDictionary<IPEndPoint, SocketMessageClient> _socketClientsHotSwap = new HotSwapDictionary<IPEndPoint, SocketMessageClient>();

        public IEnumerable<SocketCommunicatorEx> ServerClients
        {
            get
            {
                SocketMessageServer server = _server;
                if (server == null)
                {
                    return new SocketMessageClient[] { };
                }

                return server.ClientsOnly;
            }
        }

        public bool IsServerStarted
        {
            get
            {
                SocketMessageServer server = _server;
                if (server == null)
                {
                    return false;
                }

                return server.IsStarted;
            }
        }

        public IEnumerable<SocketMessageClient> Clients
        {
            get
            {
                return _socketClientsHotSwap.Values;
            }
        }

        public delegate void TracerNetUpdateDelegate(NetworkTracer tracer);
        public event TracerNetUpdateDelegate TracerNetUpdateEvent;

        /// <summary>
        /// Constructor.
        /// </summary>
        public NetworkTracer()
        {
        }

        /// <summary>
        /// Constructor, with server start on specified port.
        /// </summary>
        public NetworkTracer(int? serverPort)
        {
            StartTraceServer(serverPort);
        }

        /// <summary>
        /// Start a trace server, that provides trace items to all that connect to it.
        /// </summary>
        /// <param name="port">Pass null for default port.</param>
        public bool StartTraceServer(int? port)
        {
            if (port.HasValue == false)
            {
                port = DefaultPort;
            }

            SocketMessageServer server = _server;
            lock (_syncRoot)
            {
                if (server != null)
                {// Already started.
                    return false;
                }

                _server = new SocketMessageServer(new BinarySerializer());
                _server.ClientConnectedEvent += new SocketMessageServer.ServerClientUpdateDelegate(_server_ClientConnectedEvent);
                _server.ClientDisconnectedEvent += new SocketMessageServer.ServerClientUpdateDelegate(_server_ClientDisconnectedEvent);

                server = _server;
            }

            if (server.Start(new System.Net.IPEndPoint(System.Net.IPAddress.Any, port.Value)) == false)
            {
                server.Dispose();
                _server = null;
                return false;
            }

            // Add this as item sink.
            base.Add(this);

            RaiseTracerNetUpdateEvent();

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        public bool StopTraceServer()
        {
            SocketMessageServer server;
            lock (_syncRoot)
            {
                server = _server;
                _server = null;
            }

            if (server == null)
            {
                return false;
            }

            // Remove this as item sink.
            base.Remove(this);

            server.Dispose();
            RaiseTracerNetUpdateEvent();

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        public bool DisconnectTraceServerClient(SocketCommunicator communicator)
        {
            SocketMessageServer server;
            lock (_syncRoot)
            {
                server = _server;
                _server = null;
            }

            if (server == null)
            {
                return false;
            }

            return server.DisconnectClient(communicator.Id);
        }

        void _server_ClientConnectedEvent(SocketMessageServer server, SocketCommunicatorEx client)
        {
            TracerItemKeeperSink keeperSink = (TracerItemKeeperSink)this.GetSinkByType(typeof(TracerItemKeeperSink));
            if (keeperSink != null)
            {
                List<TracerItem> items = new List<TracerItem>();
                lock (keeperSink.SyncRoot)
                {// Load all items.
                    items.AddRange(keeperSink.ItemsUnsafe);
                }

                int startItem = Math.Max(0, items.Count - AtConnectMaxItemsSendCount);
                for (int i = startItem; i < items.Count; i++)
                {
                    SendToClients(items[i]);
                }
            }

            RaiseTracerNetUpdateEvent();
        }

        void _server_ClientDisconnectedEvent(SocketMessageServer server, SocketCommunicatorEx client)
        {
            RaiseTracerNetUpdateEvent();
        }

        protected void RaiseTracerNetUpdateEvent()
        {
            TracerNetUpdateDelegate del = TracerNetUpdateEvent;
            if (del != null)
            {
                del(this);
            }
        }


        /// <summary>
        /// Start a trace listener to that remote address.
        /// </summary>
        /// <param name="remoteEndPoint"></param>
        /// <returns></returns>
        public bool StartTraceListen(IPEndPoint remoteEndPoint)
        {
            SocketMessageClient client = new SocketMessageClient(new BinarySerializer());
            if (_socketClientsHotSwap.TryAddValue(remoteEndPoint, client) == false)
            {
                // Failed to add listener to that end point.
                return false;
            }

            if (client.ConnectAsync(remoteEndPoint) == false)
            {
                _socketClientsHotSwap.Remove(remoteEndPoint);
                client.Dispose();
                return false;
            }

            client.AutoReconnect = true;
            client.MessageReceivedEvent += new SocketCommunicator.MessageUpdateDelegate(client_MessageReceivedEvent);
            client.ConnectedEvent += new SocketCommunicator.HelperUpdateDelegate(client_ConnectedEvent);
            client.DisconnectedEvent += new SocketCommunicator.HelperUpdateDelegate(client_DisconnectedEvent);

            RaiseTracerNetUpdateEvent();

            return true;
        }

        void client_ConnectedEvent(SocketCommunicator helper)
        {
            RaiseTracerNetUpdateEvent();
        }

        void client_DisconnectedEvent(SocketCommunicator helper)
        {
            RaiseTracerNetUpdateEvent();
        }

        void client_UpdatedEvent(SocketCommunicator helper)
        {
            RaiseTracerNetUpdateEvent();
        }

        /// <summary>
        /// 
        /// </summary>
        public bool StopTraceListen(IPEndPoint remoteEndPoint)
        {
            SocketMessageClient client;
            if (_socketClientsHotSwap.TryGetValue(remoteEndPoint, out client) == false)
            {
                // Failed to get listener to that end point.
                return false;
            }

            if (_socketClientsHotSwap.Remove(remoteEndPoint) == false)
            {// Failed to remove it.
                return false;
            }

            client.MessageReceivedEvent -= new SocketCommunicator.MessageUpdateDelegate(client_MessageReceivedEvent);
            client.ConnectedEvent -= new SocketCommunicator.HelperUpdateDelegate(client_ConnectedEvent);
            client.DisconnectedEvent -= new SocketCommunicator.HelperUpdateDelegate(client_DisconnectedEvent);

            client.Dispose();

            RaiseTracerNetUpdateEvent();

            return true;
        }

        /// <summary>
        /// Received a trace from remote listener.
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="message"></param>
        void client_MessageReceivedEvent(SocketCommunicator helper, object message)
        {
            if (message is TracerItem == false)
            {
                return;
            }

            TracerItem item = message as TracerItem;
            item.SourceId = helper.EndPoint.ToString();
            base.Add(item);
        }

        protected void SendToClients(TracerItem item)
        {
            SocketMessageServer server = _server;
            if (server != null)
            {
                server.SendAsync(item, null);
            }
        }

        #region ITracerItemSink Members

        public bool ReceiveItem(TracerItem item, bool isFilteredOut)
        {
            if (item.InstanceMonitor != null)
            {// Skip any items that were generated from the socket classes,
                // since otherwise endless loops may occur, with the item
                // generating a send, and the send - an item.

                SocketMessageServer server = _server;
                if (server != null && item.InstanceMonitor == _server.Monitor)
                {
                    return true;
                }

                foreach (SocketCommunicatorEx client in ServerClients)
                {
                    if (client.Monitor == item.InstanceMonitor)
                    {
                        return true;
                    }
                }

                foreach (SocketMessageClient client in _socketClientsHotSwap.Values)
                {
                    if (client.Monitor == item.InstanceMonitor)
                    {
                        return true;
                    }
                }
            }

            SendToClients(item);
            return true;
        }

        public void Clear()
        {
            
        }

        #endregion
    }
}

#endif
